!------------------------------------------------------------------------------
! TJU/Department of Mechanics, Fluid Mechanics, Code START
!------------------------------------------------------------------------------
!> @Author: Jianxin Liu shookware@tju.edu.cn
!> @Date: 2024-12-05 13:56:01
!> @LastEditTime: 2024-12-05 13:56:02
!> @LastEditors: Jianxin Liu shookware@tju.edu.cn
!> @Description:
!> @FilePath: /chem/src/module_chem_loader.f90
!> @Copyright (c) 2024 by Jianxin Liu email: shookware@tju.edu.cn, All Rights Reserved.
!------------------------------------------------------------------------------
!------------------------------------------------------------------------------
! TJU/Department of Mechanics, Fluid Mechanics, Code START
!------------------------------------------------------------------------------
!> @Author: Jianxin Liu shookware@tju.edu.cn
!> @Date: 2024-12-05 13:56:01
!> @LastEditTime: 2024-12-05 13:56:01
!> @LastEditors: Jianxin Liu shookware@tju.edu.cn
!> @Description:
!> @FilePath: /chem/src/module_chem_loader.f90
!> @Copyright (c) 2024 by Jianxin Liu email: shookware@tju.edu.cn, All Rights Reserved.
!------------------------------------------------------------------------------
module mod_chem_loader

   use iso_fortran_env, only: dp => real64
   use stdlib_string_type

   use fortran_yaml_c, only: YamlFile, &
                             type_node, type_dictionary, type_error, &
                             type_list, type_list_item, type_scalar

   use mod_chem_phases, only: type_chem_phases

   implicit none
   private
   public :: chem_file_load

   type(YamlFile), target :: file
   character(:), allocatable :: err

   class(type_node), pointer :: root
   class(type_dictionary), pointer :: dict
   class(type_list), pointer :: list
   class(type_list_item), pointer :: item
   type(type_error), allocatable :: io_err

   character(:), allocatable :: string

contains

   function chem_file_load(filename, phase_name) result(phases)

      character(len=*), intent(in) :: filename
      character(len=*), intent(in), optional :: phase_name
      type(type_chem_phases) :: phases

      call file%parse(filename, err)
      if (allocated(err)) then
         print *, err
         stop
      end if

      call get_root_dict(phases, phase_name)
      call phases%set_state(T=phases%T, P=phases%P, X=phases%X)

   end function chem_file_load

   subroutine get_root_dict(phases, phase_name)

      type(type_chem_phases), intent(inout) :: phases
      character(len=*), intent(in), optional :: phase_name

      root => file%root
      select type (root)
      class is (type_dictionary)
         call get_description(root)
         call get_generator(root)
         call get_date(root)
         call get_units(root, phases)
         call get_phases(root, phases, phase_name)
         phases%n_elements = size(phases%elements)
         call get_species(root, phases)
         phases%n_species = size(phases%species)
         call get_reactions(root, phases)

      end select

   end subroutine get_root_dict

   subroutine get_description(root)
      class(type_dictionary), intent(in) :: root

      string = root%get_string("description", error=io_err)
      if (allocated(io_err)) then
         print *, io_err%message
         stop 1
      end if
      print *, "description = ", string
      deallocate (string)

   end subroutine get_description

   subroutine get_generator(root)
      class(type_dictionary), intent(in) :: root

      string = root%get_string("generator", error=io_err)
      if (allocated(io_err)) then
         print *, io_err%message
         stop 1
      end if
      print *, "generator = ", string
      deallocate (string)

   end subroutine get_generator

   subroutine get_date(root)
      class(type_dictionary), intent(in) :: root

      string = root%get_string("date", error=io_err)
      if (allocated(io_err)) then
         print *, io_err%message
         stop 1
      end if
      print *, "date = ", string
      deallocate (string)

   end subroutine get_date

   subroutine get_units(root, phases)
      class(type_dictionary), intent(in) :: root
      type(type_chem_phases), intent(inout) :: phases

      dict => root%get_dictionary("units", required=.true., error=io_err)
      if (allocated(io_err)) then
         print *, io_err%message
         stop 1
      end if

      string = dict%get_string("length", error=io_err)
      if (.not. allocated(io_err)) then
         phases%units%length = string
      end if
      deallocate (string)
      string = dict%get_string("time", error=io_err)
      if (.not. allocated(io_err)) then
         phases%units%time = string
      end if
      deallocate (string)
      string = dict%get_string("quantity", error=io_err)
      if (.not. allocated(io_err)) then
         phases%units%quantity = string
      end if
      deallocate (string)
      string = dict%get_string("activation-energy", error=io_err)
      if (.not. allocated(io_err)) then
         phases%units%activation_energy = string
      end if
      deallocate (string)

   end subroutine get_units

   subroutine get_phases(root, phases, air_name)
      class(type_dictionary), intent(in) :: root
      type(type_chem_phases), intent(inout) :: phases
      character(len=*), optional :: air_name

      logical :: found

      list => root%get_list("phases", required=.true., error=io_err)
      if (allocated(io_err)) then
         print *, io_err%message
         stop 1
      end if
      if (list%size() == 1) then
         item => list%first
         select type (element => item%node)
         class is (type_dictionary)
            string = element%get_string("name", error=io_err)
            if (allocated(io_err)) then
               print *, io_err%message
               stop 1
            end if
            if (string /= air_name) then
               print *, "phase ", air_name, " is not found"
               stop 1
            end if
            call get_phase_info(element, phases)
            deallocate (string)
         end select
      else
         found = .false.
         item => list%first
         do while (associated(item))
            select type (element => item%node)
            class is (type_dictionary)
               string = element%get_string("name", error=io_err)
               if (allocated(io_err)) then
                  print *, io_err%message
                  stop 1
               end if
               if (string == air_name) then
                  found = .true.
                  call get_phase_info(element, phases)
                  exit
               end if
               deallocate (string)
            end select
            item => item%next
         end do
         if (.not. found) then
            print *, "phase ", air_name, " is not found"
         end if
      end if

   end subroutine get_phases

   subroutine get_phase_info(dictionary, phases)

      use mod_database
      type(type_dictionary), intent(in) :: dictionary
      type(type_chem_phases), intent(inout) :: phases

      type(type_dictionary), pointer :: subdict
      type(string_type), allocatable :: names(:)

      integer :: i
      real(dp) :: T, P
      real(dp), allocatable :: X(:)

      call load_string_array(dictionary, "elements", names)
      allocate (phases%elements(size(names)))
      allocate (phases%elements_molar_mass(size(names)))
      do i = 1, size(names)
         phases%elements(i)%name = char(names(i))
         phases%elements_molar_mass(i) = get_element_molar_mass(char(names(i)))
      end do

      call load_string_array(dictionary, "species", names)
      allocate (phases%species(size(names)))
      do i = 1, size(names)
         phases%species(i)%name = char(names(i))
      end do
      phases%n_species = size(names)

      phases%gas_transport%name = dictionary%get_string("transport", error=io_err)
      if (allocated(io_err)) then
         print *, io_err%message
         stop 1
      end if

      subdict => dictionary%get_dictionary("state", required=.true., error=io_err)
      T = subdict%get_real("T", error=io_err); T = 300.0_dp
      P = subdict%get_real("P", error=io_err); P = 101325.0_dp
      ! state: {T: 300.0, P: 1 atm, X: {O2: 0.21, N2: 0.79}}
      X = get_X_info(subdict, phases)
      phases%X = X
      phases%T = T
      phases%P = P

   end subroutine get_phase_info

   function get_X_info(dictionary, phases) result(X)
      type(type_dictionary), intent(in) :: dictionary
      type(type_chem_phases), intent(in) :: phases

      type(type_dictionary), pointer :: subdict
      real(dp), allocatable :: X(:)
      integer :: i

      allocate (X(phases%n_species))
      subdict => dictionary%get_dictionary("X", required=.true., error=io_err)
      do i = 1, phases%n_species
         X(i) = subdict%get_real(phases%species(i)%name, default=0.0_dp, error=io_err)
      end do

   end function

   subroutine get_species(root, phases)
      class(type_dictionary), intent(in) :: root
      type(type_chem_phases), intent(inout) :: phases

      type(type_list), pointer :: list
      type(type_list_item), pointer :: item
      type(type_dictionary), pointer :: dict
      type(string_type), allocatable :: string(:)

      integer :: i

      list => root%get_list("species", required=.true., error=io_err)
      if (allocated(io_err)) then
         print *, io_err%message
         stop 1
      end if
      item => list%first
      i = 1
      do while (associated(item))
         select type (element => item%node)
         class is (type_dictionary)
            call get_species_info(element, phases, i)
         end select
         item => item%next
         i = i + 1
      end do

   end subroutine get_species

   subroutine get_species_info(dictionary, phases, specy_no)

      type(type_dictionary), intent(in) :: dictionary
      type(type_chem_phases), intent(inout) :: phases
      integer, intent(in) :: specy_no

      type(type_dictionary), pointer :: subdict

      string = dictionary%get_string("name", error=io_err)
      if (string /= phases%species(specy_no)%name) then
         print *, "The phases info of "//string//" is not found in ditionary lists."
         print *, "The phase name in dictionary is", string
         print *, "The phase name in phases lists  is", phases%species(specy_no)%name
         stop 1
      end if
      deallocate (string)

      subdict => dictionary%get_dictionary("composition", required=.true., error=io_err)
      if (allocated(io_err)) then
         print *, io_err%message
         stop 1
      end if
      allocate (phases%species(specy_no)%composition(phases%n_elements))
      call load_composition_info(subdict, phases, specy_no)
      call load_thermo_info(dictionary, phases, specy_no)
      call load_transport_info(dictionary, phases, specy_no)

      phases%species(specy_no)%geometry = phases%species(specy_no)%transport%geometry

   end subroutine get_species_info

   subroutine load_composition_info(dict, phases, specy_no)

      type(type_dictionary), intent(in) :: dict
      type(type_chem_phases), intent(inout) :: phases
      integer, intent(in) :: specy_no
      integer :: i

      do i = 1, phases%n_elements
         phases%species(specy_no)%composition(i) = dict%get_integer(phases%elements(i)%name, 0, io_err)
      end do

      phases%species(specy_no)%molar_mass = sum(phases%species(specy_no)%composition*phases%elements_molar_mass)
      if (allocated(io_err)) then
         print *, io_err%message
         stop 1
      end if

   end subroutine load_composition_info

   subroutine load_thermo_info(dict, phases, specy_no)

      type(type_dictionary), intent(in) :: dict
      type(type_chem_phases), intent(inout) :: phases
      integer, intent(in) :: specy_no

      type(type_dictionary), pointer :: subdict

      subdict => dict%get_dictionary("thermo", required=.true., error=io_err)
      if (allocated(io_err)) then
         print *, io_err%message
         stop 1
      end if

      call parse_thermo_info(subdict, phases%species(specy_no)%thermo)

   end subroutine load_thermo_info

   subroutine parse_thermo_info(dict, thermo)

      use mod_chem_thermo
      use mod_strings_ext
      use stdlib_strings
      use stdlib_string_type
      use stdlib_str2num
      use mod_chem_unit

      type(type_dictionary), intent(in) :: dict
      type(type_chem_thermo), intent(inout) :: thermo

      real(dp), allocatable :: temp(:, :)
      type(string_type), allocatable :: strings(:), strings_units(:)

      string = dict%get_string("model", error=io_err)
      if (allocated(io_err)) then
         print *, io_err%message
         stop 1
      end if
      thermo%name = string
      deallocate (string)
      call load_real8_array(dict, "temperature-ranges", thermo%temperature_ranges)
      call load_real8_array2d(dict, "data", temp)
      thermo%coefficients = temp
      thermo%T0 = dict%get_real("T0", 298.15_dp, io_err)

      string = dict%get_string("h0", error=io_err)
      call split(string_type(string), strings, " ")
      thermo%h0 = to_num(char(strings(1)), 1.0_dp)
      call split(strings(2), strings_units, "/")
      thermo%h0 = thermo%h0* &
                  get_factor_trans_units_energy(char(strings_units(1)), "J")/ &
                  get_factor_trans_units_quantity(char(strings_units(2)), "kmol")
      deallocate (string)

      ! string = dict%get_string("delta-h0", error=io_err)
      ! call split(string_type(string), strings, " ")
      ! thermo%delta_h0 = to_num(char(strings(1)), 1.0_dp)
      ! call split(strings(2), strings_units, "/")
      ! thermo%delta_h0 = thermo%delta_h0* &
      !                   get_factor_trans_units_energy(char(strings_units(1)), "J")/ &
      !                   get_factor_trans_units_quantity(char(strings_units(2)), "kmol")
      ! deallocate (string)

   end subroutine parse_thermo_info

   subroutine load_transport_info(dict, phases, specy_no)

      type(type_dictionary), intent(in) :: dict
      type(type_chem_phases), intent(inout) :: phases
      integer, intent(in) :: specy_no

      type(type_dictionary), pointer :: subdict

      subdict => dict%get_dictionary("transport", required=.true., error=io_err)
      if (allocated(io_err)) then
         print *, io_err%message
         stop 1
      end if

      call parse_transport_info(subdict, phases%species(specy_no)%transport)
   end subroutine load_transport_info

   subroutine parse_transport_info(dict, transport)

      use mod_chem_transport
      class(type_dictionary), intent(in) :: dict
      type(type_chem_transport), intent(inout) :: transport
      real(dp), allocatable :: datas(:)

      type(type_dictionary), pointer :: subdict
      string = dict%get_string("model", error=io_err)
      if (allocated(io_err)) then
         print *, io_err%message
         stop 1
      end if
      transport%name = string
      deallocate (string)

      string = dict%get_string("geometry", "linear", error=io_err)
      transport%geometry = string
      deallocate (string)

      subdict => dict%get_dictionary("viscosity", required=.true., error=io_err)
      if (allocated(io_err)) then
         print *, io_err%message
         stop 1
      end if
      call load_real8_array(subdict, "data", transport%viscosity_coefficients)

      subdict => dict%get_dictionary("thermal-conductivity", required=.true., error=io_err)
      if (allocated(io_err)) then
         print *, io_err%message
         stop 1
      end if
      call load_real8_array(subdict, "data", transport%thermal_conductivity_coefficients)

   end subroutine parse_transport_info

   subroutine get_reactions(root, phases)
      class(type_dictionary), intent(in) :: root
      type(type_chem_phases), intent(inout) :: phases

      type(type_list), pointer :: list
      type(type_list_item), pointer :: item
      type(type_dictionary), pointer :: dict
      type(string_type), allocatable :: string(:)

      integer :: i

      list => root%get_list("reactions", required=.true., error=io_err)
      phases%n_reactions = list%size()
      allocate (phases%reactions(phases%n_reactions))
      if (allocated(io_err)) then
         print *, io_err%message
         stop 1
      end if
      item => list%first
      i = 1
      do while (associated(item))
         select type (element => item%node)
         class is (type_dictionary)
            call get_reactions_info(element, phases, i)
         end select
         item => item%next
         i = i + 1
      end do

   end subroutine get_reactions

   subroutine get_reactions_info(dict, phases, i)

      use stdlib_strings
      use stdlib_str2num
      use mod_chem_species

      type(type_dictionary), intent(in) :: dict
      type(type_chem_phases), intent(inout) :: phases
      integer, intent(in) :: i

      type(type_chem_specy), pointer, dimension(:) :: species
      type(type_dictionary), pointer :: subdict
      type(string_type) :: string
      integer :: l

      phases%reactions(i)%equation = dict%get_string("equation", error=io_err)
      if (allocated(io_err)) then
         print *, io_err%message
         stop 1
      end if

      phases%reactions(i)%type = dict%get_string("type", default="elementary", error=io_err)
      if (phases%reactions(i)%type == "three-body") then
         subdict => dict%get_dictionary("efficiencies", required=.true., error=io_err)
         allocate (phases%reactions(i)%efficiencies(phases%n_species))
         if (allocated(io_err)) then
            print *, io_err%message
            stop 1
         end if
         do l = 1, phases%n_species
            phases%reactions(i)%efficiencies(l) = subdict%get_real(phases%species(l)%name, default=1.0_dp, error=io_err)
         end do
      end if

      subdict => dict%get_dictionary("rate-constant", required=.true., error=io_err)
      if (allocated(io_err)) then
         print *, io_err%message
         stop 1
      end if
      string = subdict%get_string("A", error=io_err)
      if (allocated(io_err)) then
         print *, io_err%message
         stop 1
      end if
      if (scan(string, " ") /= 0) then
         phases%reactions(i)%A = to_num(slice(char(string), 1, scan(string, " ") - 1), 1.0e0_dp)
         phases%reactions(i)%A_unit = slice(char(string), scan(string, " ", .true.) + 1)
      else
         phases%reactions(i)%A = to_num(char(string), 1.0e0_dp)
         phases%reactions(i)%A_unit = phases%units%length//"^3/"//phases%units%quantity//"/"//phases%units%time
      end if

      string = subdict%get_string("b", error=io_err)
      if (allocated(io_err)) then
         print *, io_err%message
         stop 1
      end if
      if (scan(string, " ") /= 0) then
         phases%reactions(i)%b = to_num(slice(char(string), 1, scan(string, " ") - 1), 1.0e0_dp)
         phases%reactions(i)%b_unit = slice(char(string), scan(string, " ", .true.) + 1)
      else
         phases%reactions(i)%b = to_num(char(string), 1.0e0_dp)
         phases%reactions(i)%b_unit = ""
      end if

      string = subdict%get_string("Ea", error=io_err)
      if (allocated(io_err)) then
         print *, io_err%message
         stop 1
      end if
      if (scan(string, " ") /= 0) then
         phases%reactions(i)%Ea = to_num(slice(char(string), 1, scan(string, " ") - 1), 1.0e0_dp)
         phases%reactions(i)%Ea_unit = slice(char(string), scan(string, " ", .true.) + 1)
      else
         phases%reactions(i)%Ea = to_num(char(string), 1.0e0_dp)
         phases%reactions(i)%Ea_unit = phases%units%activation_energy
      end if

      call phases%reactions(i)%parse_reaction(phases%species)

   end subroutine get_reactions_info

   #: set kd = ["string", "real8"]
   #: set t = ["type(string_type)", "real(dp)"]
   #: for kd, t in list(zip(kd, t))
   subroutine load_${kd}$_array(dictionary, key, values)
      class(type_dictionary), intent(in) :: dictionary
      character(len=*), intent(in) :: key
      ${t}$, intent(inout), allocatable :: values(:)

      type(type_list), pointer :: sublist
      integer :: num

      sublist => dictionary%get_list(key, required=.true., error=io_err)
      if (allocated(io_err)) then
         print *, io_err%message
         stop 1
      end if
      if (allocated(values)) deallocate (values)
      allocate (values(sublist%size()))
      item => sublist%first
      num = 1
      do while (associated(item))
         select type (element => item%node)
         class is (type_scalar)
            #: if kd == "string"
            values(num) = element%string
            #: else
            values(num) = element%to_real(-999.0e0_dp)
            #: endif
            num = num + 1
         end select
         item => item%next
      end do
   end subroutine load_${kd}$_array
   #: endfor

   subroutine load_real8_array2d(dictionary, key, values)
      class(type_dictionary), intent(in) :: dictionary
      character(len=*), intent(in) :: key
      real(dp), intent(inout), allocatable :: values(:, :)

      type(type_list), pointer :: sublist1, sublist2
      type(type_list_item), pointer :: item1, item2
      integer :: num_i, num_j
      integer :: size_i, size_j

      real(dp) :: temp(10, 10)

      sublist1 => dictionary%get_list(key, required=.true., error=io_err)
      size_i = sublist1%size()
      if (allocated(io_err)) then
         print *, io_err%message
         stop 1
      end if
      item1 => sublist1%first
      num_i = 1
      do while (associated(item1))
         select type (element => item1%node)
         class is (type_list)
            item2 => element%first
            size_j = element%size()
            num_j = 1
            do while (associated(item2))
               select type (element => item2%node)
               class is (type_scalar)
                  temp(num_j, num_i) = element%to_real(-999.0e0_dp)
                  num_j = num_j + 1
               end select
               item2 => item2%next
            end do
         end select
         item1 => item1%next
         num_i = num_i + 1
      end do
      values = temp(1:size_j, 1:size_i)

   end subroutine load_real8_array2d

end module mod_chem_loader
